package classes;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class Graph<Vertex, Link> {
	//
	// This graph will represent links between vertex. It is important to note that it is possible
	// to have MULTIPLE links between vertices and that it is a directional graph.
	//
	private Map<Vertex, Map<Vertex, Set<Link>>>	children = new HashMap<Vertex, Map<Vertex, Set<Link>>>();
	private Map<Vertex, Map<Vertex, Set<Link>>>	parents = new HashMap<Vertex, Map<Vertex, Set<Link>>>();
	private Set<Vertex> allVertex = new HashSet<Vertex>();


	public Graph() {
		initialize();
	}

	private synchronized final void initialize() {
		//
		// This will initialize the graph to a clean slate
		//
		allVertex = initVertexSet();
		children = initNeighborMap();
		parents = initNeighborMap();
	}

	//
	// Generate and initialize a new graph. This cannot be overwritten. It ensures the data structures are empty.
	//
	private final  Set<Vertex> initVertexSet() {
		Set<Vertex>	set = generateVertexSet();
		set.clear();
		return set;
	}
	private final  Map<Vertex, Map<Vertex, Set<Link>>> initNeighborMap() {
		 Map<Vertex, Map<Vertex, Set<Link>>>	map = generateNeighborMap();
		 map.clear();
		 return map;
	}
	private final Map<Vertex, Set<Link>> initLinkMap() {
		Map<Vertex, Set<Link>>	linkMap = generateLinkMap();
		linkMap.clear();
		return linkMap;
	}

	protected Map<Vertex, Map<Vertex, Set<Link>>>	generateNeighborMap() {
		return new HashMap<Vertex, Map<Vertex, Set<Link>>>();
	}
	protected Set<Vertex>	generateVertexSet() {
		return new HashSet<Vertex>();
	}
	protected Map<Vertex, Set<Link>>	generateLinkMap() {
		return new HashMap<Vertex, Set<Link>>();
	}

	private Set<Vertex>		getAllVertexInternal() {
		if (allVertex==null) {
			allVertex = generateVertexSet();
		}
		return allVertex;
	}

	private Map<Vertex, Set<Link>>	getChildMap(Vertex parent) {
		Map<Vertex, Set<Link>>	childrenOfParent = children.get(parent);
		if (childrenOfParent==null) {
			childrenOfParent = initLinkMap();
			children.put(parent, childrenOfParent);
		}
		return childrenOfParent;
	}

	private Map<Vertex, Set<Link>>	getParentMap(Vertex child) {
		Map<Vertex, Set<Link>>	parentsOfChild = children.get(child);
		if (parentsOfChild==null) {
			parentsOfChild = initLinkMap();
			parents.put(child, parentsOfChild);
		}
		return parentsOfChild;
	}

	public synchronized  void setLinks(Vertex parent, Vertex child, Set<Link> paths) {
		getAllVertexInternal().add(parent);
		getAllVertexInternal().add(child);
		getChildMap(parent).put(child, paths);
		getParentMap(child).put(parent, paths);
	}

	public synchronized  Set<Link>	getLinks(Vertex parent, Vertex child) {
		getAllVertexInternal().add(parent);
		getAllVertexInternal().add(child);
		if (getChildMap(parent).size() < getParentMap(child).size()) {
			return getChildMap(parent).get(child);
		} else {
			return getParentMap(child).get(parent);
		}
	}

	public synchronized  Set<Vertex>	getChildren(Vertex parent) {
		getAllVertexInternal().add(parent);
		return Collections.unmodifiableSet(getChildMap(parent).keySet());
	}

	public synchronized  Set<Vertex>	getParents(Vertex child) {
		getAllVertexInternal().add(child);
		return Collections.unmodifiableSet(getParentMap(child).keySet());
	}

	public synchronized  boolean hasLink(Vertex parent, Vertex child) {
		return !getLinks(parent, child).isEmpty();
	}

	public synchronized	void addLink(Vertex parent, Vertex child, Link link) {
		getLinks(parent,child).add(link);
	}
	public synchronized void removeLink(Vertex parent, Vertex child, Link link) {
		getLinks(parent, child).remove(link);
	}
	public synchronized void addDoubleLink(Vertex vertexA, Vertex vertexB, Link link) {
		getLinks(vertexA,vertexB).add(link);
		getLinks(vertexB,vertexA).add(link);
	}
	public synchronized void removeDoubldLink(Vertex vertexA, Vertex vertexB, Link link) {
		getLinks(vertexA,vertexB).remove(link);
		getLinks(vertexB,vertexA).remove(link);
	}
	public synchronized void isolateVertex(Vertex vertex) {
		if (getAllVertexInternal().contains(vertex)) {
			children.remove(vertex);
			parents.remove(vertex);
		}
	}
	public synchronized void removeVertex(Vertex vertex) {
		isolateVertex(vertex);
		allVertex.remove(vertex);
	}
	public Set<Vertex>	getAllVertex() {
		return Collections.unmodifiableSet(getAllVertexInternal());
	}

}
